package bloque;

import java.io.FileNotFoundException;
import java.io.IOException;


public class Trie {

    private static final int tamBloque = 256;
    private int raiz = 1;

    private int aNivelProfundidad=5;
    private ArchivoTablaLocalizacionTrie lista;
    private ArchivoBloques archBloque;
    private Bloque bloqueActual;
    
    //creo la raiz con id 0 para encontrarla siempre
    public Trie() throws FileNotFoundException, IOException {
            
        this.archBloque = new ArchivoBloques("ArchivoTrie.dat",tamBloque);
        this.lista = new ArchivoTablaLocalizacionTrie("ArchivoLocalizacionTrie.dat");
        this.bloqueActual = new Bloque(0,tamBloque);                            
                    
        if (this.archBloque.getOffSetEOF()>0) {
            byte [] bytesBloqueTemporal = new byte[tamBloque];
                            
            archBloque.LeerBloque(bytesBloqueTemporal, (int)archBloque.GetCantidadBloques()-1);
            this.bloqueActual.convertirBytesABloque(bytesBloqueTemporal);  
        }
        else {
            NodoTrie nodoRaizTemporal = new NodoTrie(-1,raiz,"\\");
    
            this.bloqueActual.agregarRegistroABloque(nodoRaizTemporal.convertirNodoABytes());
            this.lista.setNumeroBloque(bloqueActual.getIdBloque());                                
        }
            
    }
    
    public Trie(int xNivelProfundidad) throws FileNotFoundException, IOException {
            
        this.aNivelProfundidad=xNivelProfundidad;
        this.archBloque = new ArchivoBloques("ArchivoBloques.dat",tamBloque);
        this.lista = new ArchivoTablaLocalizacionTrie("ArchivoLocalizacion.dat");
        this.bloqueActual = new Bloque(0,tamBloque);                            
                    
        if (this.archBloque.getOffSetEOF()>0) {
            byte [] bytesBloqueTemporal = new byte[tamBloque];
                            
            archBloque.LeerBloque(bytesBloqueTemporal, (int)archBloque.GetCantidadBloques()-1);
            this.bloqueActual.convertirBytesABloque(bytesBloqueTemporal);  
        }
        else {
            NodoTrie nodoRaizTemporal = new NodoTrie(-1,raiz,"\\");
    
            this.bloqueActual.agregarRegistroABloque(nodoRaizTemporal.convertirNodoABytes());
            this.lista.setNumeroBloque(bloqueActual.getIdBloque());                                
        }
            
    }
    
    public int ObtenerOffSetAudio(String xPalabraAReproducir){
        
        int i=0;
        int aNodoProximo =1;//hasta q le asigne algo
        int aNodoCorriente=1;//empiezo siempre desde la raiz
        
        //tambien hay q cortar por el largo de la palabra porq si esta la palabra "hojaldre" pero 
        //yo quiero "hoja" no encuentra null y sigue entonces la corto por el largo
        while ((aNodoProximo!= -1) && i < xPalabraAReproducir.length()){
            aNodoCorriente = aNodoProximo;
            String caracterAuxiliar;
            if (i< this.aNivelProfundidad-2) {
                caracterAuxiliar=xPalabraAReproducir.substring(i, i+1);
            }else {
                caracterAuxiliar=xPalabraAReproducir.substring(i, xPalabraAReproducir.length());
                i=xPalabraAReproducir.length();
            }
                aNodoProximo=buscarEnProfundidad(aNodoCorriente,caracterAuxiliar);
            i++;
        }
        
        //no estamos aplicando el /n para finalizar,q no se si es necesario
        if (aNodoProximo==-1)
        	return -1;
        else
        	return (Integer)irANodo(aNodoProximo).getData();
        
    }
    //una funcion hace todo lo mismo q el obtener offset audio pero q te devuelve hasta el ultimo nodo q encontro
    //y te devuelve  el id de ese nodo para agregar desde ese, y ademas me devuelve hasta q posicion del string encontro
    //si devuelve 0 no encontro nada si devuelve el length del string tira exception porq ya existe la palabra
    //me tiene q devolver i
    private int BuscarNodo(String xPalabraAReproducir) {
        int i=0;
        int listo = 0;
        int aNodoProximo =1;//hasta q le asigne algo
        int aNodoCorriente=1;//empiezo siempre desde la raiz
        while ((aNodoProximo!= -1) && listo ==0 && (i < xPalabraAReproducir.length())){ 
            aNodoCorriente = aNodoProximo;
            String caracterAuxiliar;
            if (i<this.aNivelProfundidad-1) {
                caracterAuxiliar=xPalabraAReproducir.substring(i, i+1);
            }else {
                caracterAuxiliar=xPalabraAReproducir.substring(i, xPalabraAReproducir.length());
                listo = 1;
            }
                aNodoProximo=buscarEnProfundidad(aNodoCorriente,caracterAuxiliar);
            i++;
        }
        
            return  aNodoCorriente;
    }
    
    public void GuardarPalabra(String xPalabraAGuardar,int offset){
    Object dato = offset;
    int pos = 0;
    String caracterAuxiliar;
    int nodo = 1;
    while (pos <= xPalabraAGuardar.length()) {
        nodo = BuscarNodo(xPalabraAGuardar);
        pos = getPosCadena(xPalabraAGuardar);
        
        if (pos<this.aNivelProfundidad-1) {
            caracterAuxiliar = xPalabraAGuardar.substring(pos-1, pos );
            pos++;
        }else {
            caracterAuxiliar = xPalabraAGuardar.substring(pos-1, xPalabraAGuardar.length());
            pos = xPalabraAGuardar.length() + 1;
        }         
            try {
                if (!(caracterAuxiliar.equals(""))) {
                	
                	if (pos==xPalabraAGuardar.length()+1)
                		add(nodo,dato,caracterAuxiliar);
                	else
                		add(nodo,-1,caracterAuxiliar);
                	
                    this.lista.setNumeroBloque(bloqueActual.getIdBloque());
                }
                
                
            } catch (IOException e) {
                System.out.println("problema al abrir el archivo de localizacion"); 
                System.out.println(e.toString());
            }
        }        
    }
    
    private int getPosCadena(String xPalabraAReproducir) {
        int listo = 0;
        int i = 0;
        int aNodoProximo =1;//hasta q le asigne algo
        int aNodoCorriente=1;//empiezo siempre desde la raiz
        while ((aNodoProximo!= -1) && listo ==0 && (i < xPalabraAReproducir.length())){ 
                aNodoCorriente = aNodoProximo;
                String caracterAuxiliar;
                if (i<this.aNivelProfundidad-1) {
                    caracterAuxiliar=xPalabraAReproducir.substring(i, i+1);
                }else {
                    listo =1;
                    caracterAuxiliar=xPalabraAReproducir.substring(i, xPalabraAReproducir.length());
                }
                    aNodoProximo=buscarEnProfundidad(aNodoCorriente,caracterAuxiliar);
                i++;
            }
            return i;        
    }

    //Agrega un nuevo hijo al trie, este es creado con el registro y el label pasado por argumento.
    // Si este nodo ya tiene un label con esta misma letra se tira una excepcion
    //Si este nodo no tenia hijos, se crea directamente
    //Devuelve el nodo agregado
    //Devuelve IlegalStateException si ese nodo ya tenia ese label    
    private void add (int DesdeNodo, Object data, String c) throws IOException
    {
        NodoTrie nodoAux = new NodoTrie();
        nodoAux = irANodo(DesdeNodo);
        if (nodoAux.getHijo() == -1)//equivalente a null
        {
            int aux= this.lista.getCantidadRegistros();
            NodoTrie nuevoNodo = new NodoTrie(data,aux+1,c);
            nodoAux.setHijo(nuevoNodo.getIdNodo());
            
            if (this.bloqueActual.agregarRegistroABloque(nuevoNodo.convertirNodoABytes())==-2) {

                archBloque.EscribirBloque(bloqueActual.convertirBloqueABytes(),bloqueActual.getIdBloque());
                bloqueActual = new Bloque((int)archBloque.GetCantidadBloques(),tamBloque);

                bloqueActual.rwBloque();
                bloqueActual.agregarRegistroABloque(nuevoNodo.convertirNodoABytes());
            }
            this.actualizarNodo(nodoAux);
        }
        else
        {
        	
        	if ((Integer)irANodo(nodoAux.getHijo()).getData()!=(Integer)data  && (irANodo(nodoAux.getHijo()).getTo_label().equals(c)))
        	{
        		nodoAux=irANodo(nodoAux.getHijo());
        		nodoAux.setData(data);
        		this.actualizarNodo(nodoAux);
        	}
        	else
        	{
        		if (!(irANodo(nodoAux.getHijo()).getTo_label()).equals(c) ){
        			addhermano(data, c,nodoAux.getHijo());
        		}
        	}
        }
    }

    //hay q implementar esto, a todo esto le falta pensarse q ademas del registro hay q ver en q bloque esta
    // q espero sea pasar un parametro mas y listo
    private NodoTrie irANodo(int id) {
    
         int encontrado=0;
         byte[] bytesBloque=new byte[tamBloque];
         Bloque bloqueAux = new Bloque(tamBloque);
         NodoTrie nodoTrieAux = new NodoTrie();
         
         try {
            int bloque = this.lista.getNumeroBloque(id);
            
            if (bloqueActual.getIdBloque()!=bloque) {
                archBloque.LeerBloque(bytesBloque,bloque);
                bloqueAux.convertirBytesABloque(bytesBloque);
            }
            else
                bloqueAux=bloqueActual;            
            
            //levantar los registros del bloque hasta encontrar el que quiero
            bloqueAux.rwBloque();
            while ((encontrado != 1)) {
                byte[] bytesRegistro = bloqueAux.getProximoRegistro();
                nodoTrieAux.convertirBytesANodo(bytesRegistro);
                
                if (nodoTrieAux.getIdNodo() == id)
                    encontrado=1;
            }
        } 
         
        catch (IOException e){
            e.printStackTrace();
        }
    
        if (encontrado==1) 
            return nodoTrieAux;
        else
            nodoTrieAux.setTo_label("\\");
            return nodoTrieAux;
    }
     
    //Agrega un nodo al hijo con el registro y el label por parametro, si ya tiene un hermano con ese label 
     //arroja una excepcion del tipo IllegalStateException. En caso de no tener hermanos , este metodo lo creo
    private void addhermano (Object data, String c,int DesdeNodo) throws IOException
    {
        NodoTrie nodoAux = new NodoTrie();
        nodoAux = irANodo(DesdeNodo);

        if (nodoAux.getHermano() == -1)
        {
            int aux= this.lista.getCantidadRegistros();
            NodoTrie nuevoNodo = new NodoTrie(data,aux+1,c);
            nodoAux.setHermano(nuevoNodo.getIdNodo());
            if (this.bloqueActual.agregarRegistroABloque(nuevoNodo.convertirNodoABytes())==-2) {

                archBloque.EscribirBloque(bloqueActual.convertirBloqueABytes(),bloqueActual.getIdBloque());
                bloqueActual = new Bloque((int)archBloque.GetCantidadBloques(),tamBloque);

                bloqueActual.rwBloque();
                bloqueActual.agregarRegistroABloque(nuevoNodo.convertirNodoABytes());
            }
            this.actualizarNodo(nodoAux);
        }
        else
        {
            if (!irANodo(nodoAux.getHermano()).getTo_label().equals(c))
                addhermano (data, c, nodoAux.getHermano());
        }
    }

    //Sigue el camino por de los labels por el string que se le pasa por parametro, si no lo encuentra
    //como camino posible devuelve null
    //Si el hijo directo tiene el label buscado devuelve ese nodo, sino llama a buscar a los hermanos del hijo
    private int buscarEnProfundidad(int nodo, String c)
    {
        NodoTrie nodoActual = new NodoTrie();
        nodoActual = irANodo(nodo);
        if (nodoActual.getHijo() == -1) return -1;
        String straux = irANodo(nodoActual.getHijo()).getTo_label();
        if (straux.equals(c))
            return nodoActual.getHijo();
        else
            return buscarEnNivel(nodoActual.getHijo(), c);
    }
    
    //Este busca entre los hermanos, recursivamente hasta encontrar el label, sino devuelve null
    private int buscarEnNivel(int nodo,String c)
    {
        
        if (getHermanoNodo(nodo) == -1) return -1;
        else {
             NodoTrie nodoActual = new NodoTrie();
               nodoActual = irANodo(getHermanoNodo(nodo));
               if (nodoActual.getTo_label().equals(c))
                    return nodoActual.getIdNodo();
                else
                    return buscarEnNivel(nodoActual.getIdNodo(), c);

        }

    }
    
     private int getHermanoNodo(int nodo) {
         
         if ((irANodo(nodo).getTo_label()).equals("\\")) return -1;
         else return irANodo(nodo).getHermano();
     }

    private void actualizarNodo (NodoTrie xNodoAActualizar) throws IOException {

         Bloque bloqueAux = new Bloque(tamBloque);
         NodoTrie nodoAux = new NodoTrie();
         
         int bloque = this.lista.getNumeroBloque(xNodoAActualizar.getIdNodo());

         if (bloque != this.bloqueActual.getIdBloque())
         {
                 byte[] bytesBloque = new byte[tamBloque];

                 archBloque.LeerBloque(bytesBloque,bloque);
                 bloqueAux.convertirBytesABloque(bytesBloque);                  
         }
         else
                 bloqueAux=bloqueActual;                
         
         //levantar los registros del bloque hasta encontrar el que quiero
         int encontrado=0;
         int offSetProximoRegistro=-1;
         bloqueAux.rwBloque();
         while ((encontrado != 1)) {
                 offSetProximoRegistro = bloqueAux.getOffSetProxmimoRegistro();
                 byte[] bytesRegistro = bloqueAux.getProximoRegistro();
                 nodoAux.convertirBytesANodo(bytesRegistro);
                 
                 if (nodoAux.getIdNodo() == xNodoAActualizar.getIdNodo())
                         encontrado=1;
         }
         
         bloqueAux.actualizarRegistro(offSetProximoRegistro, xNodoAActualizar.convertirNodoABytes());
         if (bloque != this.bloqueActual.getIdBloque())
         {
                 byte[] bytesBloque = bloqueAux.convertirBloqueABytes();
                 archBloque.EscribirBloque(bytesBloque,bloque);
         }              
 }
    
    public void imprimirTrie() throws IOException {
        
        Bloque bloqueAux = new Bloque(tamBloque);
        NodoTrie nodoAux = new NodoTrie();
        
        byte[] bytesBloqueAux = new byte[tamBloque];
        byte[] bytesRegistro=null;

        archBloque.EscribirBloque(bloqueActual.convertirBloqueABytes(),bloqueActual.getIdBloque());
        int cantidadBloques=(int)this.archBloque.GetCantidadBloques();
        
        System.out.println("Cantidad de bloques: "+cantidadBloques);        
        for(int i=0; i<cantidadBloques; i++) {

            archBloque.LeerBloque(bytesBloqueAux, i);
            bloqueAux.convertirBytesABloque(bytesBloqueAux);
      
            int j=0;
              bloqueAux.rwBloque();
            System.out.println("Id Bloque: "+bloqueAux.getIdBloque()+" Espacio Libre: "+bloqueAux.getEspacioLibre());
            do {
                    bytesRegistro = bloqueAux.getProximoRegistro();
                    if (bytesRegistro.length>0) {
                        nodoAux.convertirBytesANodo(bytesRegistro);                
                        nodoAux.imprimirNodo();
                    }
                    else
                        if (j==0) System.out.println("El bloque "+ i +" esta vacio!");
                    j++;      
            } while (bytesRegistro.length>0);
            
        }    
        
    }
    
    
    public void finalizar() throws IOException {
           
           archBloque.EscribirBloque(bloqueActual.convertirBloqueABytes(),bloqueActual.getIdBloque());

           archBloque.cerrarArchivo();
           lista.cerrarArchivo();
                           
   }

}
